package left42dead;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

/**
 * Human class, derives from Character
 */
public class Human extends Character
{
    // The age at which a huamn can start to breed.
    private static final int BREEDING_AGE = 15;
    // The age to which a human can live.
    private static final int MAX_AGE = 80;
    // The likelihood of a human breeding.
    private static final double BREEDING_PROBABILITY = 0.02;
    // The maximum number of births.
    private static final int MAX_LITTER_SIZE = 3;
    // The food value of a single human. In effect, this is the
    // number of steps a human can go before it has to eat again.
    private static final int HUMAN_FOOD_VALUE = 10;
    // Health penality if the human wants to eat
    private static final int HUMAN_FOOD_PENALITY = 1;
    // Thirsty probability when a human become vampire
    private static final double THIRSTY_PROBABILITY = 0.2;
    // A shared random number generator to control breeding.
    private static final Random rand = Randomizer.getRandom();

    private boolean hasBeenBitten;   // false, until a vampire bites this human
    private boolean hasBeenInfected; // false, until a zombie kill this human
    private int turnsSinceLastMeal;  // the human will lose health if he's too hungry
    private int age;                 // The human's age.
    private Weapon weapon;           // The human current weapon, can be null

    /**
     * Constructor of Human class. At the beginning of the game, humans just had
     * dinner, and have not been bitten yet.
     * @param healthPoints initial HP
     * @param field Field where he's located
     * @param location The location in the field where he's located
     */
    public Human (boolean randomAge, int healthPoints, Field field,
                  Location location)
    {
        super(healthPoints, field, location);
        hasBeenBitten = false;
        hasBeenInfected = false;
        age = 0;
        turnsSinceLastMeal = 0;

        // Generate random age if specified
        if (randomAge)
        {
            age = rand.nextInt(MAX_AGE);
            turnsSinceLastMeal = rand.nextInt(HUMAN_FOOD_VALUE);
        }
    }

    /**
     * This is what the humans does most of the time
     * @param newCharacters A list to add newly born humans to.
     */
    public void run (List<Character> newCharacters)
    {
        incrementAge();
        giveBirth(newCharacters);

        // search for ennemies to attack
        List<Location> ennemiesLocation = searchEnemies(location, Character.class);
        Iterator<Location> it = ennemiesLocation.iterator();
        
        while (it.hasNext())
        {
            Location where = it.next();
            if (where != null)
            {
                Character target = (Character) field.getObjectAt(where);
                useWeapon(target);
            }
        }

        // Try to find weapon
        Location newLocation = searchItem(location, Weapon.class);
        // See if it was possible to move and take weapon
        if (newLocation != null)
        {
            Weapon weapon = (Weapon) field.getObjectAt(newLocation);
            this.weapon = weapon;
            field.clear(newLocation);
            setLocation(newLocation);

        }
        
        if (newLocation == null)
        {
            // Try to find ammo if weapon
            if (weapon != null)
                newLocation = searchItem(location, Ammo.class);
            // See if it was possible to move and take Ammo
            if (newLocation != null)
            {
                Ammo ammo = (Ammo) field.getObjectAt(newLocation);
                this.getWeapon().reload(ammo);
                field.clear(newLocation);
                setLocation(newLocation);
            }

            if (newLocation == null)
            {
                // No ammo found - Try to find food
                newLocation = searchItem(location, Food.class);

                // No food found - try to attack or move
                if (newLocation == null)
                {
                    newLocation = getField().freeAdjacentLocation(location);
                    if (newLocation != null)
                    {
                        setLocation(newLocation);
                    }
                }
                // See if it was possible to move and eat food
                else if (newLocation != null)
                {
                    Food food = (Food) field.getObjectAt(newLocation);
                    eat(food);
                    field.clear(newLocation);
                    setLocation(newLocation);
                }

            }
        }
    }

    /**
     * Tell the human to look for a specific item adjacent to its current
     * location.
     * @param location Where in the field it is located.
     * @param itemCLass The item to search
     * @return Where item was found, or null if it wasn't.
     */
    public Location searchItem (Location location, Class itemClass)
    {
        Field field = this.getField();
        List<Location> adjacent = field.adjacentLocations(getLocation());
        Iterator<Location> it = adjacent.iterator();
        
        while (it.hasNext())
        {
            Location where = it.next();
            Object item = field.getObjectAt(where);
            if (item != null && itemClass.isInstance(item))
            {
                return where;
            }
        }
        return null;
    }

    /**
     * Tell the human to look for a specific item adjacent to its current
     * location.
     * @param location Where in the field it is located.
     * @param item The item to search
     * @return Where item was found, or null if it wasn't.
     */
    public List<Location> searchEnemies (Location location, Class itemClass)
    {
        List<Location> ennemiesLocation = new LinkedList<Location>();
        Field field = this.getField();
        List<Location> adjacent = field.adjacentLocations(getLocation());
        Iterator<Location> it = adjacent.iterator();
        
        while (it.hasNext())
        {
            Location where = it.next();
            Object item = field.getObjectAt(where);
            if (item != null && itemClass.isInstance(item))
            {
                ennemiesLocation.add(where);
            }
        }
        return ennemiesLocation;
    }

    /**
     * Use the current weapon to the target
     * @param target The targeted character
     */
    public void useWeapon (Character target)
    {
        // Check the human have a weapon
        if (weapon == null)
            return;

        weapon.use(target);

        if (weapon.isOutOfAmmo())
            weapon = null;
    }

    /**
     * Method triggered on each character at the end of each turn.
     */
    public void endOfTurn ()
    {
        // Increment the number of turns since the last time the human ate
        turnsSinceLastMeal++;
        // If the human is too hungry, he will lose health...
        if (turnsSinceLastMeal > HUMAN_FOOD_VALUE)
        {
            reduceHealthPoints(HUMAN_FOOD_PENALITY);
        }
    }

    /**
     * Check whether or not this rabbit is to give birth at this step. New
     * births will be made into free adjacent locations.
     * 
     * @param newRabbits A list to add newly born humans to.
     */
    private void giveBirth (List<Character> newCharacters)
    {
        giveBirth(newCharacters, rand.nextDouble(), rand.nextInt(MAX_LITTER_SIZE) + 1);
    }

    public void giveBirth (List<Character> newCharacters, double chance,
                           int children)
    {
        // New humans are born into adjacent locations.
        // Get a list of adjacent free locations.
        List<Location> free = field.getFreeAdjacentLocations(location);
        int births = breed(chance, children);
        for (int b = 0; b < births && free.size() > 0; b++)
        {
            Location loc = free.remove(0);
            Human young = new Human(false, initialHealthPoints, field, loc);
            newCharacters.add(young);
        }
    }

    /**
     * Generate a number representing the number of births, if it can breed.
     * @return The number of births (may be zero).
     */
    public int breed (double chance, int children)
    {
        int births = 0;
        if (canBreed() && chance <= BREEDING_PROBABILITY)
        {
            if (children >= MAX_LITTER_SIZE)
                births = MAX_LITTER_SIZE;
            else
                births = children;
        }
        return births;
    }

    /**
     * Eat a certain amount of food and restore health points
     * @param food The food eatten
     */
    public void eat (Food food)
    {
        // Increase health points
        increaseHealthPoints(food.getAmount());
        turnsSinceLastMeal = 0;
    }

    /**
     * An human can breed if it has reached the breeding age.
     * @return true if the human can breed, false otherwise.
     */
    public boolean canBreed ()
    {
        return (age >= BREEDING_AGE);
    }

    /**
     * Transform this human who has been bitten, into a blood-thirsty vampire.
     * @return a new object of class Vampire, with the same healthpoints
     *         as this human; the new vampire can be thirsty
     */
    public Vampire turnIntoVampire ()
    {
        Vampire newVampire = new Vampire(getHealthPoints(), field, location);
        newVampire.setIsThirsty((Randomizer.getRandom().nextDouble() < THIRSTY_PROBABILITY));
        return newVampire;
    }

    /**
     * Transform this human who has been infected, into a zombie
     * @return a new object of class Zombie, with the same healthpoints
     *         as this human
     */
    public Zombie turnIntoZombie (int healthPoints)
    {
        Zombie newZombie = new Zombie(healthPoints, field, location);
        return newZombie;
    }

    /**
     * Increase the age. This could result in the human's death.
     */
    public void incrementAge ()
    {
        age++;
        
        if (age > MAX_AGE) 
            setDead();
    }

    // Accessors and mutators
    public boolean getHasBeenBitten ()
    {
        return hasBeenBitten;
    }

    public void setHasBeenBitten (boolean hasBeenBitten)
    {
        this.hasBeenBitten = hasBeenBitten;
    }

    public Weapon getWeapon ()
    {
        return weapon;
    }

    public boolean getHasBeenInfected ()
    {
        return hasBeenInfected;
    }

    public void setHasBeenInfected (boolean hasBeenInfected)
    {
        this.hasBeenInfected = hasBeenInfected;
    }
    
    public void setAge (int age)
    {
        this.age = age;
    }

    public int getAge ()
    {
        return age;
    }

    public int getTurnsSinceLastMeal ()
    {
        return turnsSinceLastMeal;
    }

    public void setTurnsSinceLastMeal (int turns)
    {
        turnsSinceLastMeal = turns;
    }

    public void setWeapon (Weapon weapon)
    {
        this.weapon = weapon;
    }
}
